State of the Art가 별건가… 서령은 예술의 경지가 맞다. 입이 귀에 걸린 채로 먹고 온 것 같다. 잘 먹었습니다!!
洪 民憙 (Hong Minhee)
@hongminhee@hackers.pub · 1004 following · 710 followers
Hi, I'm who's behind Fedify, Hollo, BotKit, and this website, Hackers' Pub! My main account is at
@hongminhee洪 民憙 (Hong Minhee)
.
Fedify, Hollo, BotKit, 그리고 보고 계신 이 사이트 Hackers' Pub을 만들고 있습니다. 제 메인 계정은:
@hongminhee洪 民憙 (Hong Minhee)
.
Fedify、Hollo、BotKit、そしてこのサイト、Hackers' Pubを作っています。私のメインアカウントは「
@hongminhee洪 民憙 (Hong Minhee)
」に。
Website
- hongminhee.org
GitHub
- @dahlia
Hollo
- @hongminhee@hollo.social
DEV
- @hongminhee
velog
- @hongminhee
Qiita
- @hongminhee
Zenn
- @hongminhee
Matrix
- @hongminhee:matrix.org
X
- @hongminhee
오픈소스 프로젝트의 커뮤니티를 어떻게 운영해야 지속적으로 사람들이 참여하게 할 수 있을까..
@bglbgl gwyng Nix의 대안으로 Guix도 종종 거론되던데, 혹시 살펴보신 적 있으실까요?
@hongminhee洪 民憙 (Hong Minhee) 비슷한 접근이란 것만 알고 있습니다. 남은 문제를 개선하는데 어느쪽이 더 좋은 설계를 갖고있는지는 모르겠네요. 근데 Nix가 일종의 기본 레지스트리에 해당하는 nixpkgs의 규모가 더 클거 같은데, 그러면 당장 쓰기엔 수고가 덜할거 같네요.
코드 수정 제안 반영은 해당 줄을 대치하는 방식으로 처리해도 충분했는데 이제 그래프 매칭 문제로 바꿔야겠다
그동안 Nix 쓰면서 고통도 많이 받았는데 그래도 주변에 꾸준히 Nix를 권한다. Nix가 '빌드'라는 소프트웨어 개발의 아주 일반적인 문제를 한방에 푸는 방법론이기 때문이다. 물론 아직 몇가지 문제가 좀 있지만(잘 안된다, 불편하다 식의 단순한 문제는 아니다), 나 자신도 그 해결책을 위한 몇가지 아이디어를 가지고 있고, 머지않은 미래에 풀릴거라 생각한다.
UNIX 철학이 작은 기능을 확실하게 수행하는 프로그램들을 만들어 조합하자인데, ls, cat, grep 등이 그 예시다. 내가 볼땐 Nix도 생각도 거기에 해당된다. Nix도 좋은 의미로 의외로 꽤 작다.
@bglbgl gwyng Nix의 대안으로 Guix도 종종 거론되던데, 혹시 살펴보신 적 있으실까요?
그동안 Nix 쓰면서 고통도 많이 받았는데 그래도 주변에 꾸준히 Nix를 권한다. Nix가 '빌드'라는 소프트웨어 개발의 아주 일반적인 문제를 한방에 푸는 방법론이기 때문이다. 물론 아직 몇가지 문제가 좀 있지만(잘 안된다, 불편하다 식의 단순한 문제는 아니다), 나 자신도 그 해결책을 위한 몇가지 아이디어를 가지고 있고, 머지않은 미래에 풀릴거라 생각한다.
UNIX 철학이 작은 기능을 확실하게 수행하는 프로그램들을 만들어 조합하자인데, ls, cat, grep 등이 그 예시다. 내가 볼땐 Nix도 생각도 거기에 해당된다. Nix도 좋은 의미로 의외로 꽤 작다.
어제 송년회에서 오라클 이야기가 너와서 나도 써보려고 하는데... n8n이라도 올려야겠다.
Optique 문서를 보다가 argument ordering 파트에서 프로퍼티가 나타난(? appear) 순서대로 파서가 동작(? consume)한다고 되어 있어서 Object 타입인데 이게 작성한 순서대로 Object.entries() 같은 곳에서 순회되기를 기대할 수 있나 의문이 들었다(Object가 Map같은 거라고 생각했어서).
아래와 같이 타고 가면:
20.1.2.5 Object.entries ( O )(User call)7.3.23 EnumerableOwnProperties ( O, kind )(called by2. Let entryList be ? EnumerableOwnProperties(obj, key+value).10.1.11 [[OwnPropertyKeys]] ( )(called by1. Let ownKeys be ? O.[[OwnPropertyKeys]]().)10.1.11.1 OrdinaryOwnPropertyKeys ( O )(called by1. Return OrdinaryOwnPropertyKeys(O).)
아래와 같은 대목을 만나는데:
- Let keys be a new empty List.
- For each own property key P of O such that P is an array index, in ascending numeric index order, do
- Append P to keys.
- For each own property key P of O such that P is a String and P is not an array index, in ascending chronological order of property creation, do
- Append P to keys.
- For each own property key P of O such that P is a Symbol, in ascending chronological order of property creation, do
- Append P to keys.
- Return keys.
만약 key가 array index가 아닌 문자열 혹은 Symbol이라면 프로퍼티 생성 발생의 오름차순 순서(? ascending chronological order of property creation)대로 순회(?)해야한다고 적혀있다.
아마.. 잘 못 찾아서 못 본 걸수도 있지만 chronological이나 creation 같이 검색했을때 스펙에서 이를 다루는 방법을 정의하지는 않는 것 같았다. 예를 들어, PropertyDescriptor이 auto increment 되는 고유 ID를 갖고 있어야 하고 이를 통해 정렬해야한다, 거나?
실제 구현을 보고 싶어서 GitHub에 있는 V8 미러로 가서 보니 key들을 OrderedHashSet으로 갖고 있는 듯 했다. 생각해보니 그러면 되네, 싶어서 더는 안 찾아봤다.
암튼 Optique 문서대로 생성 순서대로 동작할 것 같다!
가족들이 밋업 굿즈를 탐내는 건 처음이다. 다음번엔 앵그리 버전 한 표!
洪 民憙 (Hong Minhee) shared the below article:
미소녀 보려고 미연시를 켰더니 게임 콘솔이 해킹당했어요
Helloyunho @helloyunho@hackers.pub
당신은 게임에 갇힌 미소녀들을 보기 위해 PlayStation을 켰습니다. 마침 친구가 "나 이쪽 루트 클리어했는데 너도 볼래?" 라며 세이브 파일을 주네요. 마침 그 루트로 갈 시기를 놓친 당신은 친구의 세이브를 이용해 확인해보려고 합니다. 세이브를 등록 후, 미연시를 켜서 로드했는데..? 갑자기 콘솔이 멈추며 결국 콘솔 내부 저장공간을 포맷했습니다!
... 당연히 위 내용은 실제 스토리가 아니지만, 충분히 일어날 수 있습니다. 이 글에서 설명할 내용들로 말이죠.
yarpe
yarpe(Yet Another Ren'Py PlayStation Exploit)를 소개합니다!
이 스크립트는 Ren'Py 기반의 PlayStation 게임들에서 적용되는 취약점이며, 현 시점에서 적용 가능한 게임은 다음과 같습니다:
- A YEAR OF SPRINGS PS4 (CUSA30428, CUSA30429, CUSA30430, CUSA30431)
- Arcade Spirits: The New Challengers PS4 (CUSA32096, CUSA32097)
그런데, 이 모든 것이 어떻게 시작되었고, 어떻게 만들어진걸까요?
Xbox One/Series
사실 저는 PlayStation(이하 PS로 줄여서 말하겠습니다)에 관심이 없었습니다. 반대로 Xbox에 많은 관심이 있었죠. 처음 Xbox One/Series의 커널 취약점이 생겼을 때 Warhammer: Vermintide 2의 게임 세이브 취약점을 이용한 게임 덤프가 제 눈에 잡혔습니다. 그때 문뜩 든 생각이: "다른 게임은 이런 세이브 취약점이 없을까?" 였는데요, 저와 같이 이런 작업에 관심을 두는 친구가 먼저 추천해준 것은 RPG Maker(쯔꾸르 라고도 많이 불리죠)로 만들어진 게임들이었습니다. 아쉽게도, 콘솔 버전에서 사용하는 RPG Maker 게임들은 다른 세이브 구조를 가지고있었고, ACE(Arbitrary Code Execution)가 불가능했습니다.
그러다 문뜩 생각이 났습니다: "Ren'Py 게임들이 세이브로 Pickle을 사용하지 않나?"
Pickle
Python에는 Pickle이라는 직렬화(serialization) 방식이 존재합니다. 이는 Python의 (왠만한) 모든 object를 직렬화할 수 있는 특징이 있습니다.
하지만 만약 직렬화하지 못하는 object가 class의 property로 존재하는데, 이 class를 직렬화하고 싶다면 어떻게 해야할까요? Python은 이를 위해 __reduce__라는 method를 지원합니다. 이는 class가 직렬화/역직렬화 될 때 어떤 데이터를 사용하여 class를 다시 구성해야할지 명시해줍니다.
사용법은 다음과 같습니다:
class A:
def __init__(self, a):
self.a = a
self.b = "b"
def __reduce__(self):
return self.__class__, (self.a,)
# serialize
a = A()
b = pickle.dumps(a)
그런데, 만약 __reduce__에 다른 Python 함수가 있으면 어떨까요? 예를 들어, exec 같은거라면 말이죠?
class Exploit:
def __reduce__(self):
return exec, ("print('Hello, World!'),)
exploit = Exploit()
a = pickle.dumps(exploit)
pickle.loads(a) # Hello, World!
...네, Pickle이 로딩될 때 문자열에 담긴 코드를 실행해버립니다... 이것이 Python 공식 Pickle 문서에서 Pickle이 안전하지 않다고 하는 이유겠죠.
세이브 하나로 Ren'Py 게임 가지고 놀기
이제 Ren'Py가 Pickle을 사용한다는 사실과, Pickle로 코드를 실행할 수 있다는 사실을 알았으니, 직접 실행해볼 시간입니다!
Ren'Py의 세이브는 1-1-LT1.save 같은 파일 이름을 가지고 있습니다. 멋져보이지만, 사실 그냥 Zip 파일이며, 확장자만 .save로 변경된겁니다.
이를 흔한 Zip 프로그램으로 풀어보면, 여러 파일들이 나오지만 우리가 관심있는 파일은 log 파일입니다. 이 파일이 Ren'Py의 Pickle을 담고있는 파일이죠.
이제 이 파일을 제가 만든 코드가 담긴 Pickle로 바꿔치기 하고, 다시 압축을 해서 넣으면..?

코드 실행이 됩니다! 너무 멋지네요!
코드 실행은 되는데, 이제 어쩌죠?
이제 코드 실행이 되는걸 알았으니, 다음 단계는 무엇일까요? 당연히 메모리 조작이죠! Google에서 잠시 조사한 결과 unsafe-python 이라는 저장소가 눈에 들어왔습니다. 이 저장소는 Python에서 직접적인 메모리 접근을 가능하게 합니다.
해당 취약점은 LOAD_CONST opcode가 아무 범위 검사를 하지 않는다는 점을 이용하여 가짜 PyObject를 만들 수 있고, 이를 이용하여 0부터 사실상 64비트 주소 끝자락까지의 bytearray 객체를 만들어 직접적인 메모리 접근을 합니다.
이제 우리는 메모리 주소만 알면 언제든지 해당 메모리를 수정할 수 있습니다! 덤으로, Python의 사랑스러운 slicing 문법은 이를 더 편하게 만듭니다.
# Assume we got raw memory bytearray
mem = getmem()
mem[0x18000000:0x18000008] = b'\0' * 8
이제 마음대로 메모리 조작도 가능하고, PyObject 생성도 가능하니, 저만의 프로그램을 메모리에 저장한 후 Python의 function 객체를 만들어 제 코드를 향하게 하면 끝입니다!
...가 된다면 정말 쉬울건데 말이죠...
메모리 영역 권한
메모리 영역에는 특정 권한이 부여되어 있습니다. Read, Write, eXecute 권한이 분리되어 있는데, 이름에서 알 수 있듯 execute 권한 없이는 해당 메모리 영역을 코드로서 실행할 수 없습니다.
문제되는 부분은, 보통 우리가 작성하는 영역은 read와 write만 있고, execute 권한이 없습니다! 만약 execute 권한이 없는 영역을 실행하려 한다면, CPU에서 권한 부족 오류를 발생시킬 것이고, 이는 segfault로 이어질 것입니다.
그럼 현재 부족한 메모리 권한으로 원하는 명령을 어떻게 실행할 수 있을까요? 답은 ROP에 있습니다.
ROP
ROP, Return Oriented Programming은 말 그대로 asm의 ret 명령을 기준으로 작동하는 코드를 말합니다.
ret 명령의 특징은 현재 CPU가 가리키는 stack pointer(x86_64 기준 RSP register) 에 적힌 주소 값을 instruction pointer(x86_64 기준 RIP register)에 적고 stack pointer를 움직인다는 것입니다. 그럼 ret를 실행하는 때에 stack pointer를 (실행 가능한 메모리 영역에 있는) 저희가 원하는 코드로 향하게 하면 어떨까요?
이를 하기 위해선, ret로 끝나면서 원하는 명령을 실행하는 메모리 주소를 미리 찾아놓아야 할 것입니다. 이를 우리는 gadget이라고 부릅니다.
Stack pointer에서도 권한 오류가 발생할 수 있지 않을까 하실 수도 있지만, stack pointer가 가리키는 메모리 영역은 read, write 권한만으로 충분하기 때문에 괜찮습니다.
이 사실을 알게된다면 이제 이런 구상을 할 수 있습니다:
- Python list를 통해 custom stack을 만든다.
- Custom stack에는 적절히 gadget을 배치한다.
- Stack pointer를 원하는 주소(여기선 Python list의 elements 주소)로 변경하는 gadget을 향하도록 한 Python function 객체를 만든다.
- 해당 Python function 객체를 실행한다. Stack pointer가 옮겨지고
ret가 호출되며 원하는 명령이 실행된다!
...많은 것들이 축약되있지만 대략적으로 이런 구상이 가능하죠. 이제 이를 이용해서 취약점을 만들 시간입니다!
Gadget 찾기
앞서 말했듯 ROP를 하기 위해선 적절한 gadget을 찾는 것이 중요합니다. 저는 이를 위해 ROPgadget 툴을 이용했습니다. 원하는 executable과 함께 툴을 실행하면 ret로 끝나는 모든 asm 명령들을 메모리 주소 값과 함께 찾아줍니다! (가상 메모리 주소까지 고려해서요!)
다음엔 두 가지 방법이 있습니다:
- Executable 메모리를 읽으며 gadget 주소를 동적으로 찾기
- 미리 해당 gadget들의 주소를 적어둔
dict만들기
Xbox One/Series에선 1번 방법을 사용할 수 있었지만, PS에선 후에 언급할 내용 때문에 2번 방법을 쓸 수 밖에 없었습니다.
Stack pointer를 원하는 주소로 옮기기
이제 stack pointer를 만들어둔 Python list 주소로 옮기면 되는데, 어떻게 옮길까요? 저희가 원하는건 (x86_64 기준) mov rsp, ???와 ret입니다. 여기서 저 ???부분이 중요한데, 왜냐하면 Python function 호출이 어떻게 이루어지는지 알아야하며, 실행되는 CPU와 OS의 함수 호출 convention도 알아야하기 때문입니다.
여기서 함수 호출 convention이란 함수를 호출할 때 몇번째 argument가 어떤 register에 들어가는지를 뜻합니다.
Linux/UNIX 기반 OS의 x86_64 함수 호출 convention 순서는 다음과 같습니다: RDI, RSI, RDX, RCX, R8, R9
그리고 Python function 호출은 다음과 같이 이루어집니다:
function_call(PyObject* func, PyObject *arg, PyObject *kw)
따라서 만약 mov rsp, [rdi + 0x30]; ret 라는 명령을 찾았다면, 직접 만드는 Python function 객체 안 0x30 정도 되는 곳에 원하는 stack 주소를 넣어야할 것이고, mov rsp, [rsi + 0x10]; ret 라는 명령을 찾았다면, 직접 tuple 객체를 만든 후 0x10 정도 되는 곳에 stack 주소를 저장, 만든 function 객체를 부를 때 my_func(*custom_tuple)과 같이 호출해야 할 것입니다.
다 만들었으니 실행하면 되는데... Python으로 못 돌아오고 crash?
ROP에서 가장 중요한걸 깜빡했네요. 직접 만든 stack을 실행하고 나선 다시 원래 stack으로 돌아와야겠죠.
저같은 경우는 push rbp; mov rbp, rsp; xor esi, esi; call [rdi + 0x130] 명령을 이용하여 rbp에 rsp를 저장한 후 원하는 명령을 실행하도록 만들었습니다(rdi + 0x130에는 stack pointer를 변경하는 명령이 있습니다).
이 다음 원하는 명령 실행 후 mov rsp, rbp; pop rbp; ret 명령을 통해 다시 원래 stack pointer로 돌아옵니다.
이렇게만 하면 될까요..? 아닙니다. 이렇게 하면 Python이 함수의 return value(x86_64 기준 RAX register)를 참조하려다 잘못된 값을 참조하여 오류가 발생합니다. 그럼 어떻게 해야할까요?
정답은 None 객체를 반환해주는 것입니다. 이렇게 하면 Python에게 정상적인 값을 반환하게 되며, 오류가 발생하지 않게 됩니다. (그리고 네, None도 하나의 객체입니다.)
주의할 점은 None 객체의 refcount를 1만큼 올려주어야 합니다. 그렇지 않으면 Python이 return value의 refcount를 줄이려 할 때, underflow 문제가 발생할 수 있습니다.
이것까지 마치면, 진짜로 저희가 원하는 명령을 실행할 수 있게 됩니다!
Xbox에서 테스트!
Xbox One Research 팀의 도움을 받아 Ren'Py 게임 파일을 받은 뒤 gadget을 찾고, 돌려봤습니다!

Xbox에서 먼저 테스트한 결과 정상적으로 socket을 여는데 성공했으며, 해당 socket으로 다른 Python script를 실행하는 데 성공했습니다! (참고로 해당 게임은 Python의 socket 모듈을 지원하지 않습니다.)
Xbox 같은 경우 Windows와 거의 비슷한 함수를 사용할 수 있어서 편하게 진행할 수 있었습니다.
대망의 PS...
그렇게 Xbox에서 테스팅 후 몇달 뒤, PS 해킹에도 관심이 생겨 알아보게 되었습니다.
그렇게 알게된 Xbox와의 차이점은...
- FreeBSD 기반의 OS를 사용함
- 자체적인 syscall들이 존재함
- 메모리에 올라간 실행 파일에는 ELF 해더가 없음(Import table 알 수 없음)
- 실행 파일에 기록된 모듈만 로드할 수 있음
- PS5 기준: 실행 파일이 담긴 메모리 영역을 읽을 수 없음(XOM)
...Gadget 찾기에서 2번 방법을 사용한 이유가 XOM(eXecutable Only Memory) 때문입니다. 사실 PS4에선 1번 방법을 사용할 수 있지만, 저는 PS5 게임도 지원하고 싶었습니다.
PS5 Research & Development Discord 서버의 도움을 받아 게임 파일을 받았고, 똑같이 gadget을 찾아 작성하였습니다.
위에 적힌 제약들이 있어도, 기본 작동은 비슷하기 때문에 큰 문제 없이 만들 수 있었고, 그렇게 테스트를 한 결과..!

성공적으로 작동되었고, yarpe가 탄생할 수 있었습니다.
마무리
여기까지 오는데 (중간에 쉬었지만) 거의 1년이라는 시간이 걸렸습니다. 만들면서 힘든 것 보단 재밌다는 느낌을 더 많이 받았네요. (만드는 동안은 잠자는 시간마저 줄여가며 만들었던 것 같습니다.)
마무리하기 전에, 저에게 도움이 되었던 분들을 소개하며 끝내고자 합니다.
- Xbox One Research 팀: 이 프로젝트의 시작점이 되어주었으며, 핵심 부분을 구성하는데 큰 도움이 되었습니다. (tuxuser, LukeFZ, Billy, harold님 등이 도와주셨습니다.)
- Dr.Yenyen: PS4/5 게임들의 파일을 제공해주셨고, 많은 테스트를 진행해주셨습니다.
- Gezine: 취약점을 개발하며 제가 궁금했던 부분이나 잘못된 부분을 답변/지적 해주셨습니다.
- Sajjad: Dr.Yenyen님과 함께 많은 테스트를 진행해주셨습니다.
- cow: 직접 파일 대조까지 해주시며 문제가 되는 부분을 고쳐주셨습니다.
- earthonion: 테스트를 진행해주셨으며 많은 조언을 해주셨습니다.
긴 글 읽어주셔서 감사합니다.
tauri로 패스워드 툴 만드는 이유, 레거시 프로덕트의 지속 여부를 결정할 때 llm을 어찌 썼는지, claude skill 활용 방법, 오라클 클라우드 쓰면 왜 좋은가, 개발자가 개발을 좋아하냐, 좋아 해야만 하냐, 개발자의 ethic 등... 2025년 라스트 개발 밋업이 알차네요.
LLM 도움이면 못할 것도 없을 듯 하여 블로그를 직접 만들고 싶은데, 스프링부트 + 그냥JS로 만들어도 괜찮을지 모르겠습니다. (사실 아는 게 그것 뿐입니다..) 혹시 조금 더 편리하고 좋은 스택이 있을까요?
지금 만드는 걸 좀 어떻게든 해야 보이든 할 텐데. UK Gov의 컴포넌트를 굉장히 참고하고 있습니다. 'looking good!'을 신경쓰다보면 쉬운 길은 많죠. 장애인 관련 홈페이지를 만들고 있습니다.
어제 해커스펍 송년회를 잘 다녀왔습니다. 이동하는 버스 안에서 제 해커스펍 주소를 QR 코드로 열심히 만들었는데, 막상 공유할 용기는 없어서 못 했네요 ㅋㅋ
전날까지도 갈까 말까 고민을 많이 했었는데, 부담감을 이기고 다녀오길 정말 잘했다는 생각이 들었습니다. 라이트닝 토크도 하나같이 유익했어요.
저는 클로드 코드는 아직 써보지 않았는데 (토큰이 살살 녹는다는 얘기를 들어서요 ㅋ ), 이야기를 듣다 보니 요즘 대세는 확실히 클로드 코드인가 싶더라고요. 이제 써볼까 생각만 하고 있었는데, 어제 라이트닝 토크 발표를 듣고 나니 “바로 시작해봐야겠다”는 생각이 들었습니다. 🐱💧
집에서 멀어서 좀 늦게 도착. 아무튼 왔다.
역시 전반적으로 클로드를 많이들 쓰시는구나- 하는 인상
해커스펍 송년회 인기 폭발이라 참여를 못하고 튕겨져 나왔어요 ㅎㅎ. 서로 말귀!가 통하는 사람들끼리 네트워킹 시간을 가질 수 있는 귀한 기회였습니다.
해커스펍 송년회를 다녀왔습니다. 발표를 라이트하게 가져간다 해서 라이트하게 질문 편하게 했는데, 질문 총량이 넘어가진 않았나 걱정될 정도로 많이 한 것같아 살짝 불안하지만, 재밌었습니다.
이미지를 리사이징, 다른 포맷으로 변환하는데, imagemagick은 올바르게 이미지를 처리하는데, imgage-rs, mozjpeg는 알 수 없는 점이 잔뜩 생긴다. 일단 되는 것 찾았으니...
https://github.com/makachanm/knife
개인적으로 진행하는 풀스택 프로젝트로써 단순한 싱글-유저 액티비티펍 블로그 플랫폼인 Knife를 제작중이다. 직접 이 플랫폼으로 글을 가끔 끼적이면서 조금씩 개선시킬 생각이다.
제 지인 분이 GitHub 에서 인종차별적 코멘트를 받으셨습니다. GitHub 계정이 있으시면 신고 부탁드립니다. 영어가 어려우시더라도 LLM으로 신고글 써달라고 하면 잘 써줍니다. 신고는 단 시간 내에 많이 찍혀야 실제 보고로 올라가기 때문에 가능하신 분들은 꼭 신고 부탁드립니다.
오늘 해커스펍 송년회에서 오라클 사용해도 될까? 주제로 발표한 로빈입니다! 행사 정말 재밌었고요 오라클 쓰는 이야기 GraphQL 이야기 플랫폼 비즈니스의 방향성 이야기 등등 마음껏 해주세요~
오늘 Hackers' Public @ Seoul 송년회에서 이야기한 "코딩 에이전트와 함께 이세계 던전 탐험하기: 새로운 환경의 코드 베이스를 빠르게 분석하는 방법"의 발표 슬라이드는 [다음 링크]에서 다운로드 받으실 수 있습니다! :)
RISC-V Mainboard For the Framework Laptop 13 Is Now Available https://hardware.slashdot.org/story/25/02/04/217248/risc-v-mainboard-for-the-framework-laptop-13-is-now-available?utm_source=rss1.0mainlinkanon
오늘 Hackers' Public @ Seoul 송년회에서 "개발자는 개발을 좋아해야 하는가? 에 대한 고찰"로 라이트닝 토크를 진행했습니다. 사실 제가 발표자인줄도 모르고 앞에서 이야기를 진행하게 되었는데, 많은 분께서 호응해주시고 또 경험에서 우러나온 진솔한 의견을 내주셔서 다양한 이야기가 오갈 수 있었습니다. 모두 감사드리며, 내년에 모두 원하시는 바 이루기를 기원하겠습니다. 🥰
Just had someone leave feedback on my F/OSS project saying “maybe that's fine if a product is focused on your Chinese community.”
I'm Korean. Every single piece of documentation is in English. There's nothing in Chinese anywhere in the project.
This kind of microaggression is exhausting. As a non-white maintainer, you deal with these assumptions constantly—people who feel entitled to your labor while casually othering you based on your name.
It chips away at your motivation. It makes you wonder why you bother.
https://github.com/dahlia/optique/issues/59#issuecomment-3678606022
루비(Ruby)의 홈페이지가 새 단장했다고 해서 들어가봤는데 꽤 예쁜데? https://www.ruby-lang.org/ko/
洪 民憙 (Hong Minhee) shared the below article:
Subagent는 Tool Use입니다.
자손킴 @jasonkim@hackers.pub
지난 글에서는 Tool Use가 무엇이고 어떻게 동작하는지 알아보았다. 이번 글에서는 subagent가 Tool Use 위에서 어떻게 동작하는지 알아볼 것이다.
Tool Use 정리
Subagent 설명에 앞서 Tool Use를 간단히 정리해보고 넘어가자.
┌─────────────────────────────────────────────────────────────┐
│ 메인 메시지 루프 │
│ │
│ ┌──────┐ request ┌─────────┐ API call ┌─────┐ │
│ │ User │ ─────────────► │ Agent │ ────────────► │ LLM │ │
│ │ │ ◄───────────── │ │ ◄──────────── │ │ │
│ └──────┘ response │ │ tool_use └─────┘ │
│ │ │ │ │
│ │ ▼ │ │
│ ┌──────────────┐ │
│ │ Tools │ │
│ │ (Bash, Read, │ │
│ │ Glob, ...) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
Tool Use는 LLM이 외부 도구를 호출할 수 있게 해주는 메커니즘이다. Agent는 LLM에게 사용 가능한 도구 목록을 제공하고 LLM은 필요할 때 tool_use 응답을 반환한다. Agent는 해당 도구를 실행하고 결과를 다시 LLM에게 전달한다. 이 과정이 반복되면서 복잡한 작업을 수행한다.
Subagent란?
Subagent는 특정 작업에 특화된 AI 에이전트다. 각 subagent는 자신만의 컨텍스트 윈도우에서 독립적으로 작동하며 완료되면 결과를 메인 에이전트에게 반환한다.
Claude Code에서 subagent는 Task라는 도구로 구현되어 있다. 메인 에이전트의 시스템 프롬프트에는 Task 도구의 description으로 사용 가능한 모든 subagent 목록과 각각의 용도가 포함된다. 메인 에이전트는 이 description을 참고하여 적절한 subagent를 선택한다. 예를 들어 "코드베이스 구조 파악"이 필요하면 Explore를, "데이터베이스 스키마 설계"가 필요하면 database-schema-architect를 호출한다.
Subagent의 구조
Subagent는 마크다운 파일로 정의되고 YAML frontmatter에 다음과 같은 설정을 포함한다.
---
name: code-reviewer
description: Expert code review specialist. Use immediately after writing or modifying code.
tools: Read, Grep, Glob, Bash
model: sonnet
---
You are a senior code reviewer ensuring high standards of code quality and security.
When invoked:
1. Run git diff to see recent changes
2. Focus on modified files
3. Begin review immediately
Review checklist:
- Code is simple and readable
- Functions and variables are well-named
...
각 필드의 역할은 다음과 같다:
| 필드 | 설명 |
|---|---|
name |
subagent의 고유 식별자 |
description |
언제 이 subagent를 사용해야 하는지 설명 |
tools |
이 subagent가 사용할 수 있는 도구 목록 |
model |
사용할 모델 (sonnet, opus, haiku 또는 inherit) |
YAML frontmatter 아래의 마크다운 본문이 subagent의 시스템 프롬프트가 된다. 이 프롬프트는 subagent가 호출될 때 API 요청의 system 필드에 주입되어 subagent의 행동을 정의한다.
이 subagent 정의는 메인 에이전트의 시스템 프롬프트에 있는 tools 섹션의 Task 도구 설명에 포함된다:
{
"name": "Task",
"description": "Launch a new agent to handle complex, multi-step tasks autonomously.
Available agent types and the tools they have access to:
- Explore: Fast agent specialized for exploring codebases. (Tools: All tools)
- Plan: Software architect agent for designing implementation plans. (Tools: All tools)
- database-schema-architect: Use this agent when you need to design database schemas...
...
"
}
Task 도구의 input schema는 다음과 같다:
{
"type": "object",
"properties": {
"subagent_type": {
"type": "string",
"description": "The type of subagent to launch"
},
"prompt": {
"type": "string",
"description": "The task to delegate to the subagent"
},
"description": {
"type": "string",
"description": "Short description of what the agent will do"
}
}
}
subagent_type: 호출할 subagent 종류 (Explore, Plan, general-purpose 등)prompt: subagent에게 전달할 작업 지시. 이 내용이 subagent의 user 메시지가 된다description: 3-5단어의 짧은 요약. 로그나 UI에 표시된다
Subagent 호출 과정
실제 메시지 흐름을 통해 subagent가 어떻게 동작하는지 살펴보자.
1단계: 메인 에이전트가 Task 도구 호출
사용자가 "선물을 주고받는 기능을 위한 entity 설계"를 요청했다고 하자. 메인 에이전트(Opus 4.5)는 먼저 코드베이스를 탐색해야 한다고 판단하고 Explorer subagent를 호출한다:
{
"content": [
{
"type": "thinking",
"thinking": "사용자가 선물을 주고받는 기능을 위한 entity 설계를 요청했습니다. Plan mode가 활성화되어 있으므로, 먼저 코드베이스를 탐색하여 기존 구조를 이해해야 합니다.\n\nPhase 1에 따라:\n1. 기존 entity 구조와 패턴을 이해하기 위해 Explore agent를 사용\n2. 선물 관련 기존 구현이 있는지 확인\n3. 사용자와 아이템 관련 entity가 어떻게 구성되어 있는지 파악\n\n먼저 Explore agent를 사용하여 기존 entity 구조를 파악하겠습니다."
},
{
"type": "text",
"text": "선물 주고받기 기능을 위한 entity 설계를 시작하겠습니다. 먼저 기존 코드베이스의 entity 구조와 관련 패턴을 탐색하겠습니다."
},
{
"type": "tool_use",
"id": "toolu_01QjShG7gnF36vLgGKCWBNjg",
"name": "Task",
"input": {
"subagent_type": "Explore",
"prompt": "이 NestJS TypeScript 프로젝트에서 entity 구조를 탐색해주세요:\n\n1. 기존 entity 파일들의 위치와 구조 파악...",
"description": "Entity 구조 탐색"
}
}
]
}
thinking 블록에서 메인 에이전트는 현재 상황(Plan mode 활성화)을 파악하고 Explore agent를 선택한 이유를 명시적으로 추론했다.
2단계: 새로운 메시지 루프에서 Subagent 실행
Agent는 Task 도구 호출을 받으면 완전히 새로운 메시지 루프를 시작한다. 이 때 위에서 보았던 YAML frontmatter 하단의 마크다운 본문이 subagent 전용 시스템 프롬프트로 주입된다.
{
"model": "claude-haiku-4-5-20251001",
"system": [
{
"type": "text",
"text": "You are Claude Code, Anthropic's official CLI for Claude."
},
{
"type": "text",
"text": "You are a file search specialist for Claude Code...
=== CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
- Creating new files
- Modifying existing files
- Deleting files
...
Your strengths:
- Rapidly finding files using glob patterns
- Searching code and text with powerful regex patterns
- Reading and analyzing file contents
..."
}
],
"messages": [
{
"role": "user",
"content": "<system-reminder>\nAs you answer the user's questions, you can use the following context:\n# claudeMd\nCodebase and user instructions are ... <system-reminder>"
},
{
"role": "user",
"content": "이 NestJS TypeScript 프로젝트에서 entity 구조를 탐색해주세요..."
}
],
"tools": [
{"name": "Bash", ...},
{"name": "Glob", ...},
{"name": "Grep", ...},
{"name": "Read", ...}
]
}
- 모델: Explorer는 빠른 탐색을 위해 Haiku 4.5를 사용한다 (메인은 Opus 4.5)
- 시스템 프롬프트: 읽기 전용 모드로 제한된 전용 프롬프트가 주입된다
- 도구 제한: 메인 에이전트가 가진 모든 도구가 아닌, 탐색에 필요한 도구만 제공된다
- CLAUDE.md 전달: 프로젝트의
CLAUDE.md내용이 user 메시지의<system-reminder>태그 안에 포함되어 전달된다. 따라서 subagent도 프로젝트 컨벤션을 따른다 - 메시지: 메인 에이전트의 prompt가 user 메시지로 전달된다
3단계: Subagent의 도구 사용
Subagent도 자신만의 메시지 루프에서 도구를 사용한다:
{
"content": [
{
"type": "text",
"text": "이 NestJS 프로젝트의 entity 구조를 철저하게 탐색하겠습니다."
},
{
"type": "tool_use",
"id": "toolu_01VHysdohmuy9r58aVdcUQRh",
"name": "Glob",
"input": {
"pattern": "**/*.entity.ts"
}
},
{
"type": "tool_use",
"id": "toolu_01QYrMBhzbXpExYwgCFM7MLb",
"name": "Bash",
"input": {
"command": "find /Volumes/project/src -type f -name \"*.ts\" | grep -E \"(entity|entities)\" | head -20",
"description": "Find entity files in src directory"
}
}
]
}
Explorer subagent는 두 개의 도구를 병렬로 호출했다. Glob으로 entity 파일 패턴을 찾고 Bash로 디렉토리를 탐색한다. 이 과정이 반복되면서 subagent는 필요한 정보를 수집한다.
4단계: Subagent 완료 및 결과 반환
Subagent가 탐색을 완료하면 수집한 정보를 정리하여 최종 응답을 반환한다. 이 응답은 메인 에이전트의 Task 도구 호출에 대한 tool_result로 전달된다:
┌─────────────────────────────────────────────────────────────────────┐
│ 메인 메시지 루프 │
│ │
│ User ──► Agent ──► LLM(Opus) │
│ │ │
│ ▼ tool_use: Task │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Subagent 메시지 루프 (Explorer) │ │
│ │ │ │
│ │ prompt ──► Agent ──► LLM(Haiku) │ │
│ │ │ │ │
│ │ ▼ tool_use │ │
│ │ ┌─────────┐ │ │
│ │ │ Glob │ │ │
│ │ │ Bash │ │ │
│ │ │ Read │ │ │
│ │ └─────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 최종 응답 │ │
│ └────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ tool_result │
│ Agent 계속 진행 │
│ │
└─────────────────────────────────────────────────────────────────────┘
메인 에이전트는 Explorer의 조사 결과를 바탕으로 다음 단계(예: database-schema-architect 호출)를 진행한다.
마무리
지금까지 subagent의 동작 방식에 대해서 알아보았다. 핵심을 정리하면:
Subagent는 Tool Use다. 메인 에이전트가 Task 도구를 호출하면, 새로운 메시지 루프가 생성되어 전용 시스템 프롬프트와 제한된 도구로 작업을 수행한다.
왜 사용하는가?
- 컨텍스트 분리: 탐색/분석 과정이 메인 대화를 오염시키지 않는다
- 전문화: 각 subagent가 특정 작업에 최적화된 프롬프트와 도구를 가진다
- 효율성: 목적에 맞게 가벼운 모델(Haiku)이나 무거운 모델(Opus)을 취사선택
주의할 점:
- Subagent는 메인 대화 히스토리를 모른다. 필요한 정보는 Task의 prompt에 명시해야 한다 (단,
CLAUDE.md는 자동 전달됨) - Subagent는 subagent를 호출할 수 없다 (무한 중첩 방지를 위해 Task 도구를 사용하지 않는다.)
- 각 호출은 새로운 컨텍스트에서 시작한다 (단, resume 파라미터로 이전 대화 이어가기 가능)
결국 subagent는 Tool Use 패턴의 확장이다. 단순한 Function Call이라면 코드 실행 결과를 텍스트로 반환하지만 subagent는 별도의 메시지 루프에서 LLM이 생성한 텍스트를 반환한다는 차이만 있다. 메인 에이전트 입장에서는 둘 다 tool_result로 받는 텍스트일 뿐이다.
IM이 굉장히 시스템 로레벨에서 키입력을 후킹해서 입력값을 변조하는 방식으로 동작하기 때문이라고 알고는 있는데 제가 개발자가 아니라서 기술적으로 설명하기는 어렵네요 ㅎㅎㅎ 근데 민희님 왜 이 계정에서는 인터랙션이 안 보일까요? 블루스카이에서 인용이 달린 걸 보고서야 알았네요! https://mastodon.online/@hongminhee@hackers.pub/115752728398255601
@nesroch檀馨 (단형/ダンキヨウ) 엇, 이상하네요… Hackers' Pub과 Mastodon 사이의 소통에 문제가 있는 것 같은데, 한 번 확인해 보도록 하겠습니다!
IM이 굉장히 시스템 로레벨에서 키입력을 후킹해서 입력값을 변조하는 방식으로 동작하기 때문이라고 알고는 있는데 제가 개발자가 아니라서 기술적으로 설명하기는 어렵네요 ㅎㅎㅎ 근데 민희님 왜 이 계정에서는 인터랙션이 안 보일까요? 블루스카이에서 인용이 달린 걸 보고서야 알았네요! https://mastodon.online/@hongminhee@hackers.pub/115752728398255601
flatpak 앱들이 gui ime가 잘 안 붙는 경우가 많은 모양이네. 흠...
@pkgupdtpkg update 샌드박싱 때문인데요, 입력기가 상당히 로레벨에서 키입력을 후킹해서 작동하기 때문에 처음 패키징할 때 입력기를 존중하도록 설정하지 않으면 애초에 입력기가 있는지조차 인식하지 못합니다. 환경변수를 강제로 override 시켜서 구멍을 poke 하면 어찌어찌 인식은 하는데 그래도 안정적으로 동작하는 경우는 굉장히 드물어요. 아래 포스트에도 썼듯이 워드프로세서 같은 프로그램은 도저히 쓸 게 못됩니다.
Gemini 3과 함께 리눅스 커널 코드를 탐험하고 있다. vdso gettimeofday와 time이 어떻게 구현이 다른지 궁금해서 찾아보고 있다. 물론 더 깊이 알 시간은 없겠지만, LLM이 아니었다면 이런 걸 들여다볼 생각조차 하지 못했겠지.
여러분!!! 모바일에서 텍스트 편집을 할 때 나오는 저 '파란색 그거'를 뭐라고 하나요???
@eatch잇창명 EatChangmyeong💕🐱 텍스트 커서(text cursor) 내지는 캐럿(caret)이라고 부르는 걸로 아는데… 이걸 궁금해 하신 게 아니려나요?
여러분!!! 모바일에서 텍스트 편집을 할 때 나오는 저 '파란색 그거'를 뭐라고 하나요???
LogTape에 이런 PR을 받았는데, 로컬에서 테스트 하려면 Windows 랩톱을 켜야 하기 때문에 귀찮아서 계속 미루고 있다… 😂
저도 Flatpak이나 Snap으로 패키징된 애플리케이션에서 다국어 IM이 제대로 동작 안 하는 경험은 많이 했는데, 문득 어째서 그런 현상이 일어나는지 궁금해지긴 하네요… 구조적으로 풀기 어려운 문제려나요? 🤔
@hongminhee洪 民憙 (Hong Minhee) 오... 감사합니다. 한번 살펴보겠습니다. 사실 Vercel AI SDK는 첫삽을 이걸로 떠버려서 어쩔수없이 쓰고있는 상태입니다. 급한 불만 끄고 좀더 나은 라이브러리로 갈아타려고 했어요.
@hongminhee洪 民憙 (Hong Minhee) Vercel AI SDK에서 제가 문제점이라고 느낀 디자인을 그대로 갖고 있네요. 사실 저도 아직 충분히 고민해보진 못했고 반대 의견은 매우 환영입니다.
제가 문제라고 느낀 부분은 Message 타입 밑에 Part가 있는 건데요. 그러니까 LLM의 응답이 플랫하게 Message[]이 아니라 Message[].Part[]가 됩니다. Part는 Plain Text거나 Tool Call일 수 있습니다. 그런데 이게 메시지를 DB에 저장하고 Streaming UI를 만들때 불편합니다. 그냥 Part를 없애고 Message만 있으면 좋겠어요.
처음에 저런식의 디자인을 한 동기를 추측해보자면, Message[]를 User/Assitant/User/Assistant/... 이렇게 번갈아 나타나는 형태를 기대하고, 그걸 만족시키려면 Assistant/Assitant 이렇게 연달아 나타나는걸 피해야하니 Part를 도입한게 아닌가 싶습니다. 근데 실제론 저 번갈아 나타나야한다는 조건이 타입으로 강제도 안되고(이건 어려우니 OK) 런타임에서 뭐라고 하지도 않아요. 그리고 실제 사용에서 연달아 나타나는걸 허용하는게 오히려 자연스럽습니다.
그래서 처음에 잠깐 잘못 생각해서 나온 디자인이, 실제론 의도한 제약을 주고있지도 못하고 그냥 쓰임만 불편하게 만들고 있는거 같습니다.
보안 업데이트: Hollo 0.6.19 릴리스
Fedify의 HTML 파싱 코드에서 발견된 보안 취약점을 수정한 Hollo 0.6.19를 릴리스했습니다.
이 취약점(CVE-2025-68475)은 ReDoS(정규 표현식 서비스 거부) 문제로, 공격자가 연합 작업 중 특수하게 조작된 HTML 응답을 보내 서비스 장애를 유발할 수 있습니다. 악성 페이로드는 작지만(약 170바이트), Node.js 이벤트 루프를 장시간 차단할 수 있습니다.
모든 Hollo 운영자분들께 즉시 버전 0.6.19로 업그레이드하실 것을 강력히 권고드립니다.
| 항목 | 상세 |
|---|---|
| CVE | CVE-2025-68475 |
| 심각도 | 높음 (CVSS 7.5) |
| 조치 | Hollo 0.6.19로 업그레이드 |
セキュリティアップデート: Hollo 0.6.19 リリース
FedifyのHTMLパースコードにおけるセキュリティ脆弱性に対応したHollo 0.6.19をリリースしました。
この脆弱性 (CVE-2025-68475) は ReDoS (正規表現によるサービス拒否) の問題であり、攻撃者がフェデレーション操作中に特別に細工されたHTMLレスポンスを送信することで、サービス停止を引き起こす可能性があります。悪意のあるペイロードは小さい (約170バイト) ですが、Node.jsのイベントループを長時間ブロックする可能性があります。
すべてのHollo運営者の皆様には、直ちにバージョン 0.6.19 へのアップグレードを強くお勧めします。
| 項目 | 詳細 |
|---|---|
| CVE | CVE-2025-68475 |
| 深刻度 | 高 (CVSS 7.5) |
| 対応 | Hollo 0.6.19 にアップグレード |
Security Update: Hollo 0.6.19 Released
We have released Hollo 0.6.19 to address a security vulnerability in Fedify's HTML parsing code.
This vulnerability (CVE-2025-68475) is a ReDoS (Regular Expression Denial of Service) issue that could allow an attacker to cause service unavailability by sending specially crafted HTML responses during federation operations. The malicious payload is small (approximately 170 bytes) but can block the Node.js event loop for extended periods.
We strongly recommend all Hollo operators upgrade to version 0.6.19 immediately.
| Field | Details |
|---|---|
| CVE | CVE-2025-68475 |
| Severity | High (CVSS 7.5) |
| Action | Upgrade to Hollo 0.6.19 |
보안 업데이트: Hollo 0.6.19 릴리스
Fedify의 HTML 파싱 코드에서 발견된 보안 취약점을 수정한 Hollo 0.6.19를 릴리스했습니다.
이 취약점(CVE-2025-68475)은 ReDoS(정규 표현식 서비스 거부) 문제로, 공격자가 연합 작업 중 특수하게 조작된 HTML 응답을 보내 서비스 장애를 유발할 수 있습니다. 악성 페이로드는 작지만(약 170바이트), Node.js 이벤트 루프를 장시간 차단할 수 있습니다.
모든 Hollo 운영자분들께 즉시 버전 0.6.19로 업그레이드하실 것을 강력히 권고드립니다.
| 항목 | 상세 |
|---|---|
| CVE | CVE-2025-68475 |
| 심각도 | 높음 (CVSS 7.5) |
| 조치 | Hollo 0.6.19로 업그레이드 |
Vercel AI SDK는 LLM이라는 훌륭한 기술을 주옥같은 인터페이스로 감싸놓았다. 정말 이해가 안가는 추상화 투성이다.
Vercel AI SDK는 LLM이라는 훌륭한 기술을 주옥같은 인터페이스로 감싸놓았다. 정말 이해가 안가는 추상화 투성이다.
오랜만에 튜사에 왔다
洪 民憙 (Hong Minhee) shared the below article:
도커로 구축한 랩에서 혼자 실습하며 배우는 네트워크 프로토콜 입문 #5-1 TLS
자손킴 @jasonkim@hackers.pub
L7 애플리케이션
전송 계층은 전송 제어를 하고 애플리케이션별로 패킷을 분류하는 것 까지만 담당한다. 애플리케이션 계층은 패킷을 애플리케이션으로 처리하고 애플리케이션과 사용자를 연결하는 계층이다.
OSI 7계층의 L5, L6을 다루지 않는 이유
OSI 7계층 모델에서는 세션 계층(L5)과 프레젠테이션 계층(L6)이 별도로 정의되어 있다. 세션 계층은 애플리케이션 간의 세션(연결) 설정, 관리, 종료를 담당하고, 프레젠테이션 계층은 데이터의 형식 변환, 암호화, 압축을 담당한다.
하지만 현대 인터넷의 근간인 TCP/IP 모델에서는 이 두 계층을 애플리케이션 계층과 분리하지 않는다. TCP/IP 모델은 OSI의 L5~L7을 하나의 애플리케이션 계층으로 통합하며 세션 관리나 데이터 표현 방식은 각 애플리케이션 프로토콜이 자체적으로 처리한다. 예를 들어 TCP는 이미 전송 계층에서 연결의 설정과 해제(3-way handshake, 4-way handshake)를 관리하고 TLS는 애플리케이션 프로토콜 수준에서 암호화와 데이터 무결성을 처리한다.
실제로 RFC 3439에는 "Layering considered harmful"이라는 섹션이 있을 정도로 엄격한 계층 분리보다는 실용적인 프로토콜 설계가 중시된다. 이러한 이유로 이 책에서도 L5, L6을 별도로 다루지 않고 애플리케이션 프로토콜로 통합하여 설명한다.
다양한 프로토콜
이 책에서는 HTTP, SSL/TLS, DNS, DHCP에 대해서 다룬다.
-
HTTP(Hypertext Transfer Protocol): 웹 브라우저와 웹 서버 간의 통신을 위한 프로토콜이다. 요청-응답 방식으로 동작하며, 웹 페이지, 이미지, API 데이터 등 다양한 리소스를 전송한다.
-
SSL/TLS(Secure Sockets Layer/Transport Layer Security): 네트워크 통신을 암호화하여 보안을 제공하는 프로토콜이다. HTTPS는 HTTP에 TLS를 결합한 것으로 웹에서 가장 널리 사용되는 보안 통신 방식이다.
-
DNS(Domain Name System): 도메인 이름(예: www.example.com)을 IP 주소로 변환하는 시스템이다. 사용자가 기억하기 쉬운 도메인 이름을 사용하여 웹사이트에 접속할 수 있게 해준다.
-
DHCP(Dynamic Host Configuration Protocol): 네트워크에 연결된 장치에게 IP 주소, 서브넷 마스크, 기본 게이트웨이, DNS 서버 등의 네트워크 설정을 자동으로 할당하는 프로토콜이다.
HTTP는 따로 정리하지 않을 것이고, TLS, DNS, DHCP에 대해서만 정리 할 것이다.
TLS
TLS(SSL)은 애플리케이션을 암호화하는 프로토콜이다.
SSL에서 TLS로의 전환
SSL은 1995년 Netscape가 웹 통신 보안을 위해 개발한 프로토콜이다. SSL 2.0이 최초로 공개되었지만 심각한 보안 취약점이 발견되어 1996년 SSL 3.0으로 대체되었다. 이후 IETF(Internet Engineering Task Force)가 SSL을 표준화하는 과정에서 프로토콜 이름이 TLS(Transport Layer Security)로 변경되었다. 1999년 TLS 1.0이 RFC 2246으로 발표되었는데 이는 SSL 3.0을 기반으로 하되 상호 운용성이 없을 정도로 충분한 차이가 있었다.
SSL 3.0은 2014년 POODLE(Padding Oracle On Downgraded Legacy Encryption) 공격 취약점이 발견된 후 2015년 공식적으로 폐기되었다. TLS 1.0과 1.1도 2020년 주요 브라우저들에 의해 지원이 중단되었고 2021년 RFC 8996을 통해 공식 폐기되었다.
현재는 TLS 1.2(2008년 출시)와 TLS 1.3(2018년 출시)이 사용되며 TLS 1.3이 권장된다.
책에서는 TLS 1.2와 RSA를 기반으로 설명하고 있지만 이 포스팅에서는 TLS 1.3과 Ed25519, X25519를 기반으로 정리 할 것이다.
TLS로 막을 수 있는 위협
TLS는 스푸핑, 변조, 도청이라는 세 가지 주요 보안 위협을 방지한다.
암호화로 도청 방지
도청은 통신 당사자가 아닌 제3자가 네트워크를 흐르는 데이터를 몰래 가로채 읽는 행위이다. 공공 와이파이에서 로그인 정보를 훔치거나 네트워크 패킷을 캡처하여 민감한 정보를 탈취하는 것이 대표적인 예다.
암호화는 정해진 규칙(암호화 알고리즘)에 따라 데이터를 변환하는 기술이다. TLS는 대칭키 암호화를 사용하여 통신 내용을 암호문으로 변환한다. 도청자가 암호화된 패킷을 가로채더라도 복호화 키 없이는 원본 데이터를 알 수 없다.
해싱으로 변조 방지
변조(Tampering)는 통신 중인 데이터를 제3자가 중간에서 가로채어 내용을 바꾸는 행위이다. 예를 들어 은행 송금 요청에서 수신자 계좌번호나 금액을 변경하는 중간자 공격(Man-in-the-Middle Attack)이 있다.
해싱은 불규칙한 길이의 데이터에서 정해진 계산(해싱 알고리즘)에 따라 고정된 길이의 데이터(해시값)를 생성하는 기술이다. TLS는 메시지 인증 코드(MAC)를 사용하여 각 메시지에 해시 기반 태그를 붙인다. 수신자는 받은 데이터로 동일한 해시를 계산하고 송신자가 보낸 MAC 태그와 비교한다. 만약 데이터가 조금이라도 변경되었다면 해시값이 완전히 달라지므로 변조를 즉시 탐지할 수 있다.
디지털 인증서로 스푸핑 방지
스푸핑(Spoofing)은 공격자가 다른 서버나 사용자로 위장하여 통신 상대방을 속이는 행위이다. 가짜 은행 웹사이트를 만들어 사용자의 로그인 정보를 탈취하는 피싱 공격이 대표적이다.
디지털 인증서는 인터넷에 있는 다른 단말에 "나는 진짜입니다!"라고 증명하는 파일이다. TLS는 신뢰할 수 있는 인증 기관(CA, Certificate Authority)이 발급한 디지털 인증서를 사용하여 서버의 신원을 검증한다. 클라이언트는 서버가 제시한 인증서가 신뢰할 수 있는 CA에 의해 서명되었는지 인증서의 도메인이 접속하려는 도메인과 일치하는지 확인한다. 이 검증을 통해 가짜 서버에 연결되는 것을 방지한다.
TLS를 지탱하는 기술
TLS는 암호화 알고리즘, 키 교환 알고리즘, 디지컬 서명 알고리즘, 메시지 인증 알고리즘 4가지 기술을 조합하여 사용한다.
암호화 알고리즘
암호화는 평문(원본 데이터)을 암호문(읽을 수 없는 형태)으로 변환하는 과정이며 복호화는 암호문을 다시 평문으로 되돌리는 과정이다. TLS에서 실제 데이터 암호화에는 대칭키(공통키) 암호화 방식을 사용한다.
대칭키 암호화는 암호화와 복호화에 동일한 키를 사용하는 방식이다. AES-GCM이나 ChaCha20-Poly1305 같은 알고리즘이 대표적이며 처리 속도가 빨라 대용량 데이터 암호화에 적합하다.
하지만 대칭키 암호화에는 근본적인 문제가 있다. 통신을 시작하기 전에 양측이 동일한 키를 가지고 있어야 하는데 이 키를 어떻게 안전하게 전달할 것인가? 키를 평문으로 네트워크에 전송하면 도청자에게 탈취당할 수 있다. 키가 탈취되면 해당 키로 암호화된 모든 통신 내용이 노출된다. 이것이 바로 '키 전달 문제'이며 이를 해결하기 위해 키 교환 알고리즘이 필요하다.
키 교환 알고리즘
공통키 암호 방식을 사용하면 키 전달시 보안 문제를 피할 수 없다. 키 교환 알고리즘은 도청자가 지켜보는 공개 채널을 통해서도 양측이 안전하게 공유 비밀(Shared Secret)을 생성할 수 있게 해주는 기술이다.
RSA의 문제점
이전에는 RSA 키 교환을 사용했다. 클라이언트가 무작위 비밀값을 생성하고 서버의 RSA 공개키로 암호화하여 전송하면 서버가 자신의 개인키로 복호화하는 방식이다. 하지만 이 방식에는 심각한 문제가 있다.
만약 공격자가 암호화된 통신을 모두 저장해두었다가, 나중에 서버의 RSA 개인키가 유출되면 과거의 모든 통신을 복호화할 수 있다. 이를 '전방 비밀성(Forward Secrecy)'이 없다고 한다.
X25519로의 전환
TLS 1.3에서는 RSA 키 교환이 완전히 제거되고, X25519(또는 ECDHE) 같은 임시(Ephemeral) Diffie-Hellman 키 교환만 사용한다. X25519는 Curve25519 타원 곡선을 기반으로 한 ECDH(Elliptic Curve Diffie-Hellman) 키 교환 함수로 Daniel J. Bernstein이 2006년에 설계했다.
X25519의 장점은 다음과 같다:
- 완전 순방향 비밀성(Perfect Forward Secrecy): 매 세션마다 새로운 임시 키 쌍을 생성하므로 서버의 인증서 개인키가 유출되어도 과거 세션의 통신 내용을 복호화할 수 없다.
- 높은 성능: 256비트 키로 128비트 보안 수준을 제공하면서도 기존 알고리즘보다 훨씬 빠르다.
- 구현 안전성: 타이밍 공격 등 부채널 공격에 강하도록 설계되었다.
X25519 키 교환 동작 원리
- 키 쌍 생성: 영희와 철수는 각각 32바이트의 무작위 개인키(a, b)를 생성한다.
- 공개키 계산: 각자 자신의 개인키와 타원 곡선의 기준점(G)을 곱하여 공개키를 계산한다. 영희의 공개키 = a × G, 철수의 공개키 = b × G
- 공개키 교환: 영희와 철수는 자신의 공개키를 상대방에게 전송한다. 이 공개키는 도청자가 볼 수 있어도 안전하다.
- 공유 비밀 계산: 영희는 자신의 개인키(a)와 철수의 공개키(b × G)를 곱하여 공유 비밀을 계산한다. 철수는 자신의 개인키(b)와 영희의 공개키(a × G)를 곱한다. 타원 곡선의 수학적 특성에 의해 a × (b × G) = b × (a × G)가 성립하므로, 양측은 동일한 공유 비밀을 얻는다.
- 세션 키 유도: 공유 비밀은 HKDF(HMAC-based Key Derivation Function)를 통해 실제 암호화에 사용할 세션 키로 변환된다.
도청자는 공개키(a × G, b × G)만 볼 수 있는데 여기서 개인키(a, b)를 알아내는 것은 타원 곡선 이산 로그 문제(ECDLP)를 푸는 것으로 현재 기술로는 계산적으로 불가능하다.
디지털 서명 알고리즘
앞서 언급했듯이 TLS는 디지털 인증서에 포함된 디지털 서명을 통해 상대방이 제3자가 신뢰 할 수있는 상대인지 여부를 판단한다.
RSA에서 Ed25519로
예전에는 RSA가 디지털 서명에 널리 사용되었지만 몇 가지 한계가 있다. 동등한 보안 수준을 위해 훨씬 큰 키 크기가 필요하고(RSA 3072비트 ≈ Ed25519 256비트) 서명 생성 속도가 상대적으로 느리며 구현 시 패딩 오라클 공격 등에 취약할 수 있다.
Ed25519는 이러한 문제를 해결한 현대적인 디지털 서명 알고리즘이다. Edwards 곡선 기반의 EdDSA(Edwards-curve Digital Signature Algorithm) 구현체로, Daniel J. Bernstein 팀이 설계했다. 2023년 FIPS 186-5에 공식 포함되어 미국 연방 정부 시스템에서도 승인된 서명 알고리즘이 되었다.
Ed25519의 특징
- 작은 키와 서명 크기: 공개키 32바이트, 서명 64바이트로 매우 컴팩트하다.
- 빠른 성능: 서명 생성이 RSA보다 약 33배 빠르다.
- 높은 보안성: 128비트 보안 수준을 제공하며, 부채널 공격에 강하도록 설계되었다.
- 결정적 서명: 난수 생성기에 의존하지 않아 구현 오류로 인한 개인키 노출 위험이 없다. (Sony PlayStation 3 펌웨어 서명키 유출 사건은 ECDSA의 잘못된 난수 사용으로 발생했다.)
디지털 서명 생성과 검증 과정
서명 생성 (서버/발급자 측):
- 서명할 메시지(예: 인증서 내용)를 준비한다.
- 개인키와 메시지를 사용하여 해시를 계산한다.
- 이 해시와 개인키를 타원 곡선 연산에 사용하여 서명값(R, s)을 생성한다.
- 서명을 메시지(인증서)에 첨부한다.
서명 검증 (클라이언트 측):
- 서버로부터 인증서와 서명을 받는다.
- 인증서에 포함된 공개키를 추출한다.
- 공개키, 메시지, 서명을 사용하여 타원 곡선 방정식을 검증한다.
- 방정식이 성립하면 서명이 유효하고, 인증서가 해당 개인키 소유자에 의해 서명되었음이 증명된다.
통신 상대방 인증:
- 클라이언트가 서버에 연결하면 서버는 자신의 디지털 인증서를 제시한다.
- 인증서에는 서버의 공개키와 CA(인증 기관)의 디지털 서명이 포함되어 있다.
- 클라이언트는 이미 신뢰하고 있는 CA의 공개키로 인증서의 서명을 검증한다.
- 검증이 성공하면 인증서에 있는 서버 공개키가 진짜 해당 서버의 것임이 보장된다.
메시지 인증 알고리즘
TLS에서 앞서 언급한 디지털 서명 알고리즘은 통신 상대방을 인증하는 것일 뿐 이후 주고받는 애플리케이션 데이터(메시지)를 인증하는 것은 아니다.
디지털 서명은 비대칭키 암호화를 사용하므로 연산 비용이 높다. 매 메시지마다 서명을 생성하고 검증하는 것은 성능상 비효율적이다. 따라서 TLS는 핸드셰이크 과정에서 합의한 대칭키를 사용하는 MAC(Message Authentication Code)으로 각 메시지의 무결성과 인증을 보장한다.
MAC이란?
MAC은 메시지와 공유 비밀키를 입력으로 받아 고정 길이의 인증 태그를 생성하는 알고리즘이다. 단순한 해시와 달리, 비밀키가 없으면 올바른 MAC 태그를 생성할 수 없다. 따라서 MAC은 메시지 무결성(변조 여부)과 메시지 인증(발신자 확인)을 동시에 제공한다.
TLS에서는 주로 HMAC(Hash-based MAC)을 사용한다. HMAC은 SHA-256 같은 해시 함수와 비밀키를 결합하여 MAC 태그를 생성한다. TLS 1.3에서는 AEAD(Authenticated Encryption with Associated Data) 모드인 AES-GCM이나 ChaCha20-Poly1305를 사용하는데, 이들은 암호화와 메시지 인증을 동시에 수행한다.
MAC으로 변조 검증하는 과정
- 송신자: 암호화된 메시지와 공유 비밀키를 MAC 알고리즘에 입력하여 MAC 태그를 생성한다.
- 전송: 암호화된 메시지와 MAC 태그를 함께 전송한다.
- 수신자: 받은 메시지와 동일한 공유 비밀키로 MAC을 직접 계산한다.
- 비교: 계산한 MAC과 받은 MAC 태그를 비교한다.
- 판정: 두 값이 일치하면 메시지가 변조되지 않았음이 보장된다. 일치하지 않으면 메시지가 전송 중에 변조되었거나 올바른 키를 가진 발신자가 보낸 것이 아니므로 메시지를 폐기한다.
이 과정에서 공격자가 메시지 내용을 조금이라도 바꾸면 해시값이 완전히 달라지므로 올바른 MAC 태그를 생성할 수 없다. 또한 공유 비밀키 없이는 위조된 메시지에 대한 유효한 MAC을 만들 수 없어 메시지의 출처도 검증된다.
@hongminhee洪 民憙 (Hong Minhee) 안타깝게도 내일은 참석을 못합니다ㅠㅠ 즐거운 시간 보내십셔..
@bglbgl gwyng 헉… 아쉽게 되었네요… 😭
I want to display it somewhere...🤔
으악 송년회 오늘인줄알고 헛걸음했네
@bglbgl gwyng 허걱… 😭
🚨 Security Advisory: CVE-2025-68475
A ReDoS (Regular Expression Denial of Service) vulnerability has been discovered in Fedify's HTML parsing code. This vulnerability could allow a malicious federated server to cause denial of service by sending specially crafted HTML responses.
| CVE ID | CVE-2025-68475 |
| Severity | High (CVSS 7.5) |
| Affected versions | ≤1.9.1 |
| Patched versions | 1.6.13, 1.7.14, 1.8.15, 1.9.2 |
If you're running Fedify in production, please upgrade to one of the patched versions immediately.
For full details, see the security advisory: https://github.com/fedify-dev/fedify/security/advisories/GHSA-rchf-xwx2-hm93
Thank you to Yue (Knox) Liu for responsibly reporting this vulnerability.
@hongminhee洪 民憙 (Hong Minhee) There was a thread on SocialHub https://socialhub.activitypub.rocks/t/how-do-account-reports-federate-is-there-sample-data/2813
@silverpill Thanks!
백엔드 개발자 입장에서는 Agent Skills 를 숙지해둘 필요가 있을 것 같다.
프로덕션에서 쓰면 당연히 안되겠지만, 로컬개발환경에서 데이터를 생성할때 데이터를 생성하는 과정 자체를 skills.md 에다가 xxx 목적으로는 이런 스크립트를 실행하면 된다같은 메뉴얼 비슷한 프롬프트를 먹여주면 자기가 알아서 서커스를 해준다.
python 기반의 어플리케이션이라면 아래와 같은 커맨드를 AI 에이전트가 직접 말아서 실행해준다.
$ poetry run python -c "
import json
from todo_app.models import Todo
# Make incomplete todos as complete
todos = Todo.filter(is_completed=False)
todos.update(is_completed=True)
Ruby, Perl, Node 등등 어떤 런타임에서 돌아가도 상관없다. 몇몇 웹 프레임워크는 콘솔 기능이 내장되어 있어서 개발자 생산성에 유의미한 영향을 주기는 했지만, LLM 에이전트가 그 격차를 또 줄여주고 있는 것 같다.
어떤 데이터를 세팅하기 위해서 손으로 타이핑할 필요도 없고 LLM 에이전트가 그냥 가이드라인대로 자기가 스크립트를 짜고 직접 실행하게 하면 된다...
Docker compose로 띄우고 있다고? 그러면 이것도 LLM 에이전트가 이런식으로 서커스를 해준다.
cat script.rb | docker compose exec -T web rails runner -













